#ifndef JSimpleStringT_h__
#define JSimpleStringT_h__
#pragma once
#include <mbstring.h>
struct JStringData;
struct JStringData
{
	int nDataLength;
	int nAllocLength;
	long nRefs;     // reference count

	void* data() throw()
	{
		return (this+1);
	}

	void AddRef() throw()
	{
		_ASSERT(nRefs > 0);
		 InterlockedIncrement(&nRefs);
	}
	bool IsLocked() const throw()
	{
		return nRefs < 0;
	}
	bool IsShared() const throw()
	{
		return(nRefs > 1);
	}
	void Lock() throw()
	{
		_ASSERT(nRefs <= 1);
		nRefs--;  // Locked buffers can't be shared, so no interlocked operation necessary
		if (nRefs == 0)
		{
			nRefs = -1;
		}
	}
	void Release() throw()
	{
		_ASSERT(nRefs != 0);

		if (InterlockedDecrement(&nRefs) <= 0)
		{
			free(this);
		}
	}
	void Unlock() throw()
	{
		_ASSERT(IsLocked());

		if (IsLocked())
		{
			nRefs++;  // Locked buffers can't be shared, so no interlocked operation necessary
			if (nRefs == 0)
			{
				nRefs = 1;
			}
		}
	}
};

class JNilStringData : public JStringData
{
public:
	JNilStringData() throw() {
		nRefs = 2;
		nDataLength = 0;
		nAllocLength = 0;
		achNil[0] = 0;
		achNil[1] = 0;
	}

public:
	wchar_t achNil[2];
};

class JStringMgr
{
public:
	JStringData* Allocate(int nChars, int nCharSize ) throw()
	{
		size_t nTotalSize;
		JStringData* pData;
		size_t nDataBytes;

		_ASSERT(nCharSize > 0);

		if(nChars < 0)
		{
			_ASSERT(FALSE);
			return NULL;
		}
		nDataBytes = (nChars+1)*nCharSize;
		nTotalSize = sizeof( JStringData )+nDataBytes;
		pData = (JStringData*)malloc( nTotalSize );
		if (pData == NULL)
			return NULL;
		pData->nRefs = 1;
		pData->nAllocLength = nChars;
		pData->nDataLength = 0;

		return( pData );
	}
	void Free(JStringData* pData) throw()
	{		
		_ASSERT(pData != NULL);
		free(pData);
	}
	JStringData* Reallocate(JStringData* pData,int nChars, int nCharSize) throw()
	{
		JStringData* pNewData;
		size_t nTotalSize;
		size_t nDataBytes;

		_ASSERT(nCharSize > 0);
		nDataBytes = (nChars+1)*nCharSize;
		nTotalSize = sizeof( JStringData )+nDataBytes;
		pNewData = (JStringData*)realloc( pData, nTotalSize );
		if( pNewData == NULL )
		{
			return NULL;
		}
		pNewData->nAllocLength = nChars;

		return pNewData;
	}
	static JStringMgr* GetInstance()
	{
#pragma warning(push)
#pragma warning(disable: 4640)
		//static CWin32Heap strHeap(::GetProcessHeap());
		//static JStringMgr strMgr(&strHeap);
		static JStringMgr strMgr;
#pragma warning(pop)

		return &strMgr;
	}
	virtual JStringData* GetNilString() throw()
	{
		m_nil.AddRef();
		return &m_nil;
	}

protected:
	JNilStringData m_nil;
private:
	static bool StaticInitialize()
	{
		GetInstance();
		return true;
	}

	static bool isInitialized;
};

// Globals
__declspec(selectany) bool JStringMgr::isInitialized = JStringMgr::StaticInitialize();


template< typename BaseType = char >
class JTraitsBase
{
public:
	typedef char XCHAR;
	typedef LPSTR PXSTR;
	typedef LPCSTR PCXSTR;
	typedef wchar_t YCHAR;
	typedef LPWSTR PYSTR;
	typedef LPCWSTR PCYSTR;
};

template<>
class JTraitsBase< wchar_t >
{
public:
	typedef wchar_t XCHAR;
	typedef LPWSTR PXSTR;
	typedef LPCWSTR PCXSTR;
	typedef char YCHAR;
	typedef LPSTR PYSTR;
	typedef LPCSTR PCYSTR;
};

template< typename BaseType>
class JSimpleStringT
{
public:
	typedef typename JTraitsBase< BaseType >::XCHAR XCHAR;
	typedef typename JTraitsBase< BaseType >::PXSTR PXSTR;
	typedef typename JTraitsBase< BaseType >::PCXSTR PCXSTR;
	typedef typename JTraitsBase< BaseType >::YCHAR YCHAR;
	typedef typename JTraitsBase< BaseType >::PYSTR PYSTR;
	typedef typename JTraitsBase< BaseType >::PCYSTR PCYSTR;

public:
	explicit JSimpleStringT()
	{
		Initialize();
	}
	JSimpleStringT(_In_ const JSimpleStringT& strSrc)
	{
		JStringData* pSrcData = strSrc.GetData();
		JStringData* pNewData = CloneData( pSrcData );
		Attach( pNewData );
	}
	JSimpleStringT(	_In_z_ PCXSTR pszSrc )
	{
		JStringMgr *pStringMgr = JStringMgr::GetInstance();
		if( pStringMgr == NULL ) return;
		int nLength = StringLength( pszSrc );
		JStringData* pData = pStringMgr->Allocate( nLength, sizeof( XCHAR ) );
		if( pData == NULL )
		{
			ThrowMemoryException();
		}
		Attach( pData );
		SetLength( nLength );
		CopyChars( m_pszData, nLength, pszSrc, nLength );
	}
	JSimpleStringT(	_In_count_(nLength) const XCHAR* pchSrc,_In_ int nLength )
	{
		JStringMgr *pStringMgr = JStringMgr::GetInstance();
		_ASSERT( pStringMgr != NULL );
		if( NULL == pStringMgr) return;

		if(pchSrc == NULL && nLength != 0)
			throw(E_INVALIDARG);

		JStringData* pData = pStringMgr->Allocate( nLength, sizeof( XCHAR ) );
		if( pData == NULL )
		{
			ThrowMemoryException();
		}
		Attach( pData );
		SetLength( nLength );
		CopyChars( m_pszData, nLength, pchSrc, nLength );
	}
	void Initialize(){
		JStringMgr *pStringMgr = JStringMgr::GetInstance();
		JStringData* pData = pStringMgr->GetNilString();
		Attach( pData );
	}
	~JSimpleStringT() throw()
	{
		JStringData* pData = GetData();
		pData->Release();
	}
	JSimpleStringT& operator=(_In_ const JSimpleStringT& strSrc)
	{
		JStringData* pSrcData = strSrc.GetData();
		JStringData* pOldData = GetData();
		if( pSrcData != pOldData)
		{
			if( pOldData->IsLocked())
			{
				SetString( strSrc.GetString(), strSrc.GetLength() );
			}
			else
			{
				JStringData* pNewData = CloneData( pSrcData );
				pOldData->Release();
				Attach( pNewData );
			}
		}

		return( *this );
	}
	JSimpleStringT& operator=(PCXSTR pszSrc)
	{
		SetString( pszSrc );

		return( *this );
	}
	JSimpleStringT& operator+=(_In_ const JSimpleStringT& strSrc)
	{
		Append( strSrc );

		return( *this );
	}
	/*template <bool bMFCDLL>
	CSimpleStringT& operator+=(_In_ const CSimpleStringT<BaseType, bMFCDLL>& strSrc)
	{
	Append( strSrc );

	return( *this );
	}*/
	JSimpleStringT& operator+=(_In_z_ PCXSTR pszSrc)
	{
		Append( pszSrc );

		return( *this );
	}
	/*template< int t_nSize >
	CSimpleStringT& operator+=(_In_ const CStaticString< XCHAR, t_nSize >& strSrc)
	{
	Append( static_cast<const XCHAR *>(strSrc), strSrc.GetLength() );

	return( *this );
	}*/
	JSimpleStringT& operator+=(_In_ char ch)
	{
		AppendChar( XCHAR( ch ) );

		return( *this );
	}
	JSimpleStringT& operator+=(_In_ unsigned char ch)
	{
		AppendChar( XCHAR( ch ) );

		return( *this );
	}
	JSimpleStringT& operator+=(_In_ wchar_t ch)
	{
		AppendChar( XCHAR( ch ) );

		return( *this );
	}
	XCHAR operator[](_In_ int iChar) const
	{
		_ASSERT( (iChar >= 0) && (iChar <= GetLength()) );  // Indexing the '\0' is OK

		if( (iChar < 0) || (iChar > GetLength()) )
			throw(E_INVALIDARG);

		return( m_pszData[iChar] );
	}
	operator PCXSTR() const throw()
	{
		return( m_pszData );
	}

	void Append(_In_z_ PCXSTR pszSrc)
	{
		Append( pszSrc, StringLength( pszSrc ) );
	}
	void Append(
		_In_count_(nLength) PCXSTR pszSrc,
		_In_ int nLength)
	{
		// See comment in SetString() about why we do this
		UINT_PTR nOffset = pszSrc-GetString();

		UINT nOldLength = GetLength();
		if (nOldLength < 0)
		{
			// protects from underflow
			nOldLength = 0;
		}

		//Make sure the nLength is greater than zero
		if(nLength <0) throw (E_INVALIDARG);

		//Make sure we don't read pass end of the terminating NULL
		nLength = StringLengthN(pszSrc, nLength);

		//Make sure after the string doesn't exceed INT_MAX after appending
		if(INT_MAX - nLength < static_cast<int>(nOldLength)) throw(E_INVALIDARG);

		int nNewLength = nOldLength+nLength;
		PXSTR pszBuffer = GetBuffer( nNewLength );
		if( nOffset <= nOldLength )
		{
			pszSrc = pszBuffer+nOffset;
			// No need to call CopyCharsOverlapped, since the destination is
			// beyond the end of the original buffer
		}
		CopyChars( pszBuffer+nOldLength, nLength, pszSrc, nLength );
		ReleaseBufferSetLength( nNewLength );
	}
	void AppendChar(_In_ XCHAR ch)
	{
		UINT nOldLength = GetLength();
		int nNewLength = nOldLength+1;
		PXSTR pszBuffer = GetBuffer( nNewLength );
		pszBuffer[nOldLength] = ch;
		ReleaseBufferSetLength( nNewLength );
	}
	void Append(_In_ const JSimpleStringT& strSrc)
	{
		Append( strSrc.GetString(), strSrc.GetLength() );
	}
	//void Append(_In_ const JSimpleStringT<BaseType>& strSrc)
	//{
	//	Append( strSrc.GetString(), strSrc.GetLength() );
	//}
	void Empty() throw()
	{
		JStringData* pOldData = GetData();
		//IAtlStringMgr* pStringMgr = pOldData->pStringMgr;
		if( pOldData->nDataLength == 0 )
		{
			return;
		}

		if( pOldData->IsLocked() )
		{
			// Don't reallocate a locked buffer that's shrinking
			SetLength( 0 );
		}
		else
		{
			pOldData->Release();
			//JStringData* pNewData = pStringMgr->GetNilString();
			JStringData* pNewData = JStringMgr::GetInstance()->GetNilString();
			Attach( pNewData );
		}
	}
	void FreeExtra()
	{
		JStringData* pOldData = GetData();
		int nLength = pOldData->nDataLength;
		JStringMgr* pStringMgr = JStringMgr::GetInstance();
		if( pOldData->nAllocLength == nLength )
		{
			return;
		}

		if( !pOldData->IsLocked() )  // Don't reallocate a locked buffer that's shrinking
		{
			JStringData* pNewData = pStringMgr->Allocate( nLength, sizeof( XCHAR ) );
			if( pNewData == NULL )
			{
				SetLength( nLength );
				return;
			}

			CopyChars( PXSTR( pNewData->data() ), nLength,
				PCXSTR( pOldData->data() ), nLength );

			pOldData->Release();
			Attach( pNewData );
			SetLength( nLength );
		}
	}

	int GetAllocLength() const throw()
	{
		return( GetData()->nAllocLength );
	}
	XCHAR GetAt(_In_ int iChar) const
	{
		_ASSERT( (iChar >= 0) && (iChar <= GetLength()) );  // Indexing the '\0' is OK
		if( (iChar < 0) || (iChar > GetLength()) )
			throw(E_INVALIDARG);

		return( m_pszData[iChar] );
	}
	PXSTR GetBuffer()
	{
		JStringData* pData = GetData();
		if( pData->IsShared() )
		{
			Fork( pData->nDataLength );
		}

		return( m_pszData );
	}
	PXSTR GetBuffer(int nMinBufferLength)
	{
		return( PrepareWrite( nMinBufferLength ) );
	}
	PXSTR GetBufferSetLength(int nLength)
	{
		PXSTR pszBuffer = GetBuffer( nLength );
		SetLength( nLength );

		return( pszBuffer );
	}
	int GetLength() const throw()
	{
		return( GetData()->nDataLength );
	}
	PCXSTR GetString() const throw()
	{
		return( m_pszData );
	}
	bool IsEmpty() const throw()
	{
		return( GetLength() == 0 );
	}
	PXSTR LockBuffer()
	{
		JStringData* pData = GetData();
		if( pData->IsShared() )
		{
			Fork( pData->nDataLength );
			pData = GetData();  // Do it again, because the fork might have changed it
		}
		pData->Lock();

		return( m_pszData );
	}
	void UnlockBuffer() throw()
	{
		JStringData* pData = GetData();
		pData->Unlock();
	}
	void Preallocate(_In_ int nLength)
	{
		PrepareWrite( nLength );
	}
	void ReleaseBuffer(_In_ int nNewLength = -1)
	{
		if( nNewLength == -1 )
		{
			int nAlloc = GetData()->nAllocLength;
			nNewLength = StringLengthN( m_pszData, nAlloc);
		}
		SetLength( nNewLength );
	}
	void ReleaseBufferSetLength(_In_ int nNewLength)
	{
		_ASSERT( nNewLength >= 0 );
		SetLength( nNewLength );
	}
	void Truncate(_In_ int nNewLength)
	{
		_ASSERT( nNewLength <= GetLength() );
		GetBuffer( nNewLength );
		ReleaseBufferSetLength( nNewLength );
	}
	void SetAt(
		_In_ int iChar,
		_In_ XCHAR ch)
	{
		_ASSERT( (iChar >= 0) && (iChar < GetLength()) );

		if( (iChar < 0) || (iChar >= GetLength()) )
			throw(E_INVALIDARG);

		int nLength = GetLength();
		PXSTR pszBuffer = GetBuffer();
		pszBuffer[iChar] = ch;
		ReleaseBufferSetLength( nLength );

	}
	void SetString(PCXSTR pszSrc)
	{
		SetString( pszSrc, StringLength( pszSrc ) );
	}
	void SetString( PCXSTR pszSrc, int nLength)
	{
		if( nLength == 0 )
		{
			Empty();
		}
		else
		{
			// It is possible that pszSrc points to a location inside of our
			// buffer.  GetBuffer() might change m_pszData if (1) the buffer
			// is shared or (2) the buffer is too small to hold the new
			// string.  We detect this aliasing, and modify pszSrc to point
			// into the newly allocated buffer instead.

			if(pszSrc == NULL)
				throw(E_INVALIDARG);

			UINT nOldLength = GetLength();
			UINT_PTR nOffset = pszSrc-GetString();
			// If 0 <= nOffset <= nOldLength, then pszSrc points into our
			// buffer

			PXSTR pszBuffer = GetBuffer( nLength );
			if( nOffset <= nOldLength )
			{
				CopyCharsOverlapped( pszBuffer, GetAllocLength(),
					pszBuffer+nOffset, nLength );
			}
			else
			{
				CopyChars( pszBuffer, GetAllocLength(), pszSrc, nLength );
			}
			ReleaseBufferSetLength( nLength );
		}
	}

public:
	friend JSimpleStringT operator+(
		_In_ const JSimpleStringT& str1,
		_In_ const JSimpleStringT& str2)
	{
		JSimpleStringT s;

		Concatenate( s, str1, str1.GetLength(), str2, str2.GetLength() );

		return( s );
	}

	friend JSimpleStringT operator+(
		_In_ const JSimpleStringT& str1,
		_In_z_ PCXSTR psz2)
	{
		JSimpleStringT s;

		Concatenate( s, str1, str1.GetLength(), psz2, StringLength( psz2 ) );

		return( s );
	}

	friend JSimpleStringT operator+(
		_In_z_ PCXSTR psz1,
		_In_ const JSimpleStringT& str2)
	{
		JSimpleStringT s;

		Concatenate( s, psz1, StringLength( psz1 ), str2, str2.GetLength() );

		return( s );
	}
	static void __cdecl CopyChars(
		_Out_cap_(nChars) XCHAR* pchDest,
		_In_opt_count_(nChars) const XCHAR* pchSrc,
		_In_ int nChars) throw()
	{
		#pragma warning (push)
		#pragma warning(disable : 4996)
		memcpy( pchDest, pchSrc, nChars*sizeof( XCHAR ) );
		#pragma warning (pop)
	}
	static void __cdecl CopyChars(	XCHAR* pchDest,	size_t nDestLen,const XCHAR* pchSrc,	int nChars) throw()
	{
			memcpy_s( pchDest, nDestLen*sizeof( XCHAR ),pchSrc, nChars*sizeof( XCHAR ) );
	}
	static void __cdecl CopyCharsOverlapped(
		_Out_cap_(nChars) XCHAR* pchDest,
		_In_count_(nChars) const XCHAR* pchSrc,
		_In_ int nChars) throw()
	{
#pragma warning (push)
#pragma warning(disable : 4996)
		memmove( pchDest, pchSrc, nChars*sizeof( XCHAR ) );
#pragma warning (pop)
	}
	static void __cdecl CopyCharsOverlapped( XCHAR* pchDest,size_t nDestLen,const XCHAR* pchSrc,	int nChars) throw()
	{
			memmove_s( pchDest, nDestLen*sizeof( XCHAR ),
			pchSrc, nChars*sizeof( XCHAR ) );
	}
	static int __cdecl StringLength(const char* psz) throw()
	{
		if (psz == NULL)
		{
			return(0);
		}
		return(int(strlen(psz)));
	}
	static int __cdecl StringLength(const wchar_t* psz) throw()
	{
		if (psz == NULL)
		{
			return(0);
		}
		return(int(wcslen(psz)));
	}
	static int __cdecl StringLengthN(
		_In_opt_z_count_(sizeInXChar) const char* psz,
		_In_ size_t sizeInXChar) throw()
	{
		if( psz == NULL )
		{
			return( 0 );
		}
		return( int( strnlen( psz, sizeInXChar ) ) );
	}
	static int __cdecl StringLengthN(
		_In_opt_z_count_(sizeInXChar) const wchar_t* psz,
		_In_ size_t sizeInXChar) throw()
	{
		if( psz == NULL )
		{
			return( 0 );
		}
		return( int( wcsnlen( psz, sizeInXChar ) ) );
	}

protected:
	static void __cdecl Concatenate(
		_Inout_ JSimpleStringT& strResult,
		_In_count_(nLength1) PCXSTR psz1,
		_In_ int nLength1,
		_In_count_(nLength2) PCXSTR psz2,
		_In_ int nLength2)
	{
		int nNewLength = nLength1+nLength2;
		PXSTR pszBuffer = strResult.GetBuffer( nNewLength );
		CopyChars( pszBuffer, nLength1, psz1, nLength1 );
		CopyChars( pszBuffer+nLength1, nLength2, psz2, nLength2 );
		strResult.ReleaseBufferSetLength( nNewLength );
	}
	__declspec( noinline ) __declspec( noreturn ) static void __cdecl ThrowMemoryException()
	{
		throw ( E_OUTOFMEMORY );
	}

// Implementation
private:
	void Attach( JStringData* pData) throw()
	{
		m_pszData = static_cast< PXSTR >( pData->data() );
	}
	__declspec( noinline ) void Fork(int nLength)
	{
		JStringData* pOldData = GetData();
		int nOldLength = pOldData->nDataLength;
		JStringData* pNewData = JStringMgr::GetInstance()->Allocate( nLength, sizeof( XCHAR ) );
		if( pNewData == NULL )
		{
			throw E_OUTOFMEMORY ;
		}
		int nCharsToCopy = ((nOldLength < nLength) ? nOldLength : nLength)+1;  // Copy '\0'
		CopyChars( PXSTR( pNewData->data() ), nCharsToCopy,
			PCXSTR( pOldData->data() ), nCharsToCopy );
		pNewData->nDataLength = nOldLength;
		pOldData->Release();
		Attach( pNewData );
	}
	JStringData* GetData() const throw()
	{
		return( reinterpret_cast< JStringData* >( m_pszData )-1 );
	}
	PXSTR PrepareWrite( int nLength)
	{
		if (nLength < 0)
			throw(E_INVALIDARG);

		JStringData* pOldData = GetData();
		int nShared = 1-pOldData->nRefs;  // nShared < 0 means true, >= 0 means false
		int nTooShort = pOldData->nAllocLength-nLength;  // nTooShort < 0 means true, >= 0 means false
		if( (nShared|nTooShort) < 0 )  // If either sign bit is set (i.e. either is less than zero), we need to copy data
		{
			PrepareWrite2( nLength );
		}

		return( m_pszData );
	}
	__declspec( noinline ) void PrepareWrite2( int nLength)
	{
		JStringData* pOldData = GetData();
		if( pOldData->nDataLength > nLength )
		{
			nLength = pOldData->nDataLength;
		}
		if( pOldData->IsShared() )
		{
			Fork( nLength );
		}
		else if( pOldData->nAllocLength < nLength )
		{
			// Grow exponentially, until we hit 1G, then by 1M thereafter.
			int nNewLength = pOldData->nAllocLength;
			if( nNewLength > 1024 * 1024 * 1024 )
			{
				nNewLength += 1024 * 1024;
			}
			else
			{
				// Exponential growth factor is 1.5.
				nNewLength = nNewLength + nNewLength / 2;
			}
			if( nNewLength < nLength )
			{
				nNewLength = nLength;
			}
			Reallocate( nNewLength );
		}
	}
	__declspec( noinline ) void Reallocate(_In_ int nLength)
	{
		JStringData* pOldData = GetData();
		_ASSERT( pOldData->nAllocLength < nLength );
		JStringMgr *pStringMgr = JStringMgr::GetInstance();
		if ( pOldData->nAllocLength >= nLength || nLength <= 0)
		{
			throw  E_OUTOFMEMORY ;
			return;
		}
		JStringData* pNewData = pStringMgr->Reallocate( pOldData, nLength, sizeof( XCHAR ) );
		if( pNewData == NULL )
		{
			throw  E_OUTOFMEMORY;
		}
		Attach( pNewData );
	}

	void SetLength(_In_ int nLength)
	{
		_ASSERT( nLength >= 0 );
		_ASSERT( nLength <= GetData()->nAllocLength );

		if( nLength < 0 || nLength > GetData()->nAllocLength)
			throw(E_INVALIDARG);

		GetData()->nDataLength = nLength;
		m_pszData[nLength] = 0;
	}

	static JStringData* __cdecl CloneData(_Inout_ JStringData* pData)
	{
		JStringData* pNewData = NULL;

		JStringMgr* pNewStringMgr =JStringMgr::GetInstance();
		if( !pData->IsLocked() )
		{
			pNewData = pData;
			pNewData->AddRef();
		}
		else
		{
			pNewData = pNewStringMgr->Allocate( pData->nDataLength, sizeof( XCHAR ) );
			if( pNewData == NULL )
			{
				ThrowMemoryException();
			}
			pNewData->nDataLength = pData->nDataLength;
			CopyChars( PXSTR( pNewData->data() ), pData->nDataLength+1,
				PCXSTR( pData->data() ), pData->nDataLength+1 );  // Copy '\0'
		}

		return( pNewData );
	}
protected:
	PXSTR m_pszData;
};




#endif // JSimpleStringT_h__